package ssd8.socket.http;

import java.io.*;
import java.net.Socket;

/**
 * Class <em>HttpClient</em> is a class representing a simple HTTP client.
 *
 * @author wben
 */

public class HttpClient {

	/**
	 * default HTTP port is port 80
	 * 默认 HTTP端口，设置为 80
	 */
	private static int port = 80;

	/**
	 * Allow a maximum buffer size of 8192 bytes
	 * 允许的最大缓冲区大小，设置为 8192字节
	 */
	private static int buffer_size = 8192;

	/**
	 * Response is stored in a byte array.
	 * 字节数组，用于存储响应数据
	 */
	private byte[] buffer;

	/**
	 * My socket to the world.
	 * 用于与服务器通信的Socket对象
	 */
	Socket socket = null;

	/**
	 * Default port is 80.
	 * 默认端口，设置为80
	 */
	private static final int PORT = 80;

	/**
	 * Output stream to the socket.
	 * 输出流，用于向服务器发送数据
	 */
	BufferedOutputStream ostream = null;

	/**
	 * Input stream from the socket.
	 * 输入流，用于从服务器接收数据
	 */
	BufferedInputStream istream = null;

	/**
	 * StringBuffer storing the header
	 * 用于存储 HTTP响应头
	 */
	private StringBuffer header = null;

	/**
	 * StringBuffer storing the response.
	 * 用于存储 HTTP响应内容
	 */
	private StringBuffer response = null;
	
	/**
	 * String to represent the Carriage Return and Line Feed character sequence.
	 * 代表回车和换行的字符序列，设置为"\r\n"
	 */
	static private String CRLF = "\r\n";

	// 连接的host
	private static String host;
	/**
	 * HttpClient constructor;
	 * 构造函数
	 */
	public HttpClient() {
		buffer = new byte[buffer_size];
		header = new StringBuffer();
		response = new StringBuffer();
	}

	/**
	 * <em>connect</em> connects to the input host on the default http port --
	 * port 80. This function opens the socket and creates the input and output
	 * streams used for communication.
	 * 连接到指定的服务器和指定 80端口，打开与服务器的 Socket连接，并创建输入和输出流
	 */
	public void connect(String host) throws Exception {

		/**
		 * Open my socket to the specified host at the default port.
		 */
		socket = new Socket(host, PORT);

		/**
		 * Create the output stream.
		 */
		ostream = new BufferedOutputStream(socket.getOutputStream());

		/**
		 * Create the input stream.
		 */
		istream = new BufferedInputStream(socket.getInputStream());

		// 当前连接的主机
		this.host = host;
	}

	/**
	 * <em>processGetRequest</em> process the input GET request.
	 * 处理 HTTP GET请求。
	 * 将 GET请求发送到服务器，并等待服务器的响应，然后调用 processResponse方法来处理响应
	 */
	public void processGetRequest(String request) throws Exception {
		/**
		 * Send the request to the server.
		 */
		request += CRLF + CRLF;
		buffer = request.getBytes();
		ostream.write(buffer, 0, request.length());
		ostream.flush();
		/**
		 * waiting for the response.
		 */
		processResponse();
	}
	
	/**
	 * <em>processPutRequest</em> process the input PUT request.
	 * 处理 HTTP PUT请求
	 */
	public void processPutRequest(String request) throws Exception {
		//=======start your job here============//

		// 判断request是否合法
		String[] requestParams = request.split(" ");
		if ((requestParams.length != 3) ||
				!requestParams[0].equals("PUT") ||
				!requestParams[2].equals("HTTP/1.0")) {
			System.out.println("Bad request!");
			System.exit(-1);
		}

		// 定义待推送文件的存放地址
		String dirRoot = System.getProperty("user.dir") + "/clientDir";

		// 获取要进行put的文件路径
		String filePath = requestParams[1];
		// System.out.println(dirRoot + filePath);
		// 从文件中读取数据
		File file = new File(dirRoot + filePath);
		if (!file.exists()) {
			System.out.println("File not found: " + filePath);
			System.exit(-1);
		}

		// 获取文件的长度
		long contentLength = file.length();

		// 构建 PUT 请求头，添加Content-Length
		String newRequest = request + CRLF;
		newRequest += "Connection: close" + CRLF + "Host: " + this.host + CRLF;
		newRequest += "Content-Length: " + contentLength + CRLF + CRLF;


		// 发送 PUT 请求头
		byte[] requestBytes = newRequest.getBytes("iso-8859-1");
		ostream.write(requestBytes, 0, requestBytes.length);
		ostream.flush();

		// 定义文件输入流
		FileInputStream fis = new FileInputStream(file);
		BufferedInputStream bis = new BufferedInputStream(fis);

		// 从文件中读取数据并发送到服务器
		int readLen;
		while ((readLen = bis.read(buffer)) != -1) {
			ostream.write(buffer, 0, readLen);
			ostream.flush();
		}
		// 关闭文件输入流
		bis.close();

		// 接收和处理服务器的响应
		processResponse();
		
		//=======end of your job============//
	}
	
	/**
	 * <em>processResponse</em> process the server response.
	 * 处理服务器的 HTTP响应
	 * 分别处理响应的头部和内容，并将它们分别存储在 header和 response成员变量中
	 */
	public void processResponse() throws Exception {
		int last = 0, c = 0;
		/**
		 * Process the header and add it to the header StringBuffer.
		 */
		boolean inHeader = true; // loop control ---判断是否还在处理头部

		/**
		 * 使用 while循环来处理响应头。在循环中，从输入流 istream读取字符，并根据字符的值执行以下操作：
		 *
		 * 如果字符是\r，则跳过，不做任何处理。
		 * 如果字符是\n，则检查它是否与上一个字符 last相同。如果相同，说明出现了连续的\r\n，
		 * 这表示 HTTP响应头结束，将 inHeader设置为 false，退出循环。
		 * 否则，将字符 c添加到 header字符串缓冲区中，并更新 last为 c。
		 */
		while (inHeader && ((c = istream.read()) != -1)) {
			switch (c) {
			case '\r':
				break;
			case '\n':
				if (c == last) {
					inHeader = false;
					break;
				}
				last = c;
				header.append("\n");
				break;
			default:
				last = c;
				header.append((char) c);
			}
		}

		/**
		 * Read the contents and add it to the response StringBuffer.
		 * 读取 HTTP响应内容，将内容的字节流读取到 buffer中，
		 * 然后将 buffer中的字节数据转换为字符串，并添加到 response字符串缓冲区中
		 */
		// *** 此处原代码没有定义readLen，并且写入时将buffer全部写入，导致接受到的文件乱码
		int readLen;
		while ((readLen = istream.read(buffer)) != -1) {
			response.append(new String(buffer,0 , readLen,"iso-8859-1"));
			if (readLen < buffer.length) break;
		}
		/*
		* 关于ISO-8859-1编码格式：
		*
		* 早期的HTTP协议和服务器通常使用ISO-8859-1编码。（现在主流是utf-8）
		* 在HTTP/1.1之前的HTTP版本中，ISO-8859-1是默认的字符编码。
		* 因此，许多服务器和客户端仍然遵循这种编码方式，以确保向后兼容性
		*
		* 应用举例：tomcat服务器接受servlet请求和进行响应时，大体经历了：
		* 客户端字符串》》url编码》》服务端转ISO-8859-1编码》》客户端根据自身需要可转成UTF-8等编码
		*/
	}

	/**
	 * Get the response header.
	 * 返回存储的 HTTP响应头
	 */
	public String getHeader() {
		return header.toString();
	}

	/**
	 * Get the server's response.
	 * 返回存储的 HTTP响应内容
	 */
	public String getResponse() {
		return response.toString();
	}

	/**
	 * Close all open connections -- sockets and streams.
	 * 关闭与服务器的连接，包括Socket连接和相关的输入输出流，释放资源
	 */
	public void close() throws Exception {
		socket.close();
		istream.close();
		ostream.close();
	}
}
